<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    基于Spring Cloud微服务化开发平台Cloud-Platform完整解析.md |  
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  

</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-develop/基于Spring Cloud微服务化开发平台Cloud-Platform完整解析"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  基于Spring Cloud微服务化开发平台Cloud-Platform完整解析.md
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/develop/%E5%9F%BA%E4%BA%8ESpring%20Cloud%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%8C%96%E5%BC%80%E5%8F%91%E5%B9%B3%E5%8F%B0Cloud-Platform%E5%AE%8C%E6%95%B4%E8%A7%A3%E6%9E%90/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/develop/">develop</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">2.4k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">10 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <h4 id="小Hub领读："><a href="#小Hub领读：" class="headerlink" title="小Hub领读："></a>小Hub领读：</h4><p>讲解视频也同步发布啦，记得去看哈，一键三连哇。</p>
<p>视频讲解：<a target="_blank" rel="noopener" href="https://www.bilibili.com/video/bv1SD4y1o7cN">https://www.bilibili.com/video/bv1SD4y1o7cN</a></p>
<p>这系统，一定要学会用户-服务认证，服务-服务鉴权的那一套，这才算学会。</p>
<hr>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Cloud-Platform是国内首个基于Spring Cloud微服务化开发平台，具有统一授权、认证后台管理系统，其中包含具备用户管理、资源权限管理、网关API 管理等多个模块，支持多业务系统并行开发，可以作为后端服务的开发脚手架。代码简洁，架构清晰，适合学习和直接项目中使用。核心技术采用Spring Boot 2.1.2以及Spring Cloud (Greenwich.RELEASE) 相关核心组件，采用Nacos注册和配置中心，集成流量卫兵Sentinel，前端采用vue-element-admin组件，Elastic Search自行集成。</p>
<p>B站视频讲解：<a target="_blank" rel="noopener" href="https://www.bilibili.com/video/BV1SD4y1o7cN/">https://www.bilibili.com/video/BV1SD4y1o7cN/</a></p>
<p><strong>ps：注意本文讲解是基于Cloud-Platform v2.5版本，不是最新版本！</strong></p>
<h2 id="技术选型"><a href="#技术选型" class="headerlink" title="技术选型"></a>技术选型</h2><p>前端：vue-element-admin</p>
<p>后端：springcloud（eureka、gateway、admin、sidecar、Hystrix、feign、ribbon、zipkin）、tk+mybatis、lucene、jwt、rest</p>
<h2 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">ace-security</span><br><span class="line">  ace-modules--------------公共服务模块（基础系统、搜索、OSS）</span><br><span class="line">  ace-auth-----------------服务鉴权中心</span><br><span class="line">  ace-gate-----------------网关负载中心</span><br><span class="line">  ace-common---------------通用脚手架</span><br><span class="line">  ace-control--------------运维中心（监控、链路）</span><br><span class="line">  ace-sidebar--------------调用第三方语言服务</span><br></pre></td></tr></table></figure>

<h2 id="项目启动"><a href="#项目启动" class="headerlink" title="项目启动"></a>项目启动</h2><p><strong>须知：</strong> 因为Cloud-Platform是一个前后端分离的项目，所以后端的服务必须先启动，在后端服务启动完成后，再启动前端的工程。</p>
<h3 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h3><ul>
<li>mysql，redis，maven</li>
<li>jdk1.8</li>
<li>IDE插件一个，lombok插件，具体百度即可</li>
<li>node</li>
</ul>
<h3 id="前端"><a href="#前端" class="headerlink" title="前端"></a>前端</h3><p>git链接：<a target="_blank" rel="noopener" href="https://gitee.com/geek_qi/cloud-platform-ui">https://gitee.com/geek_qi/cloud-platform-ui</a></p>
<ul>
<li>先clone到本地，并进入cloud-platform-ui目录打开命令行窗口（cmd）</li>
<li>因为涉及node.js，所以需要安装npm，node等环境</li>
</ul>
<p>node.js安装教程：<a target="_blank" rel="noopener" href="http://nodejs.cn/download/%E4%B8%8B%E8%BD%BDmsi%E7%89%88%E6%9C%AC%E5%AE%89%E8%A3%85%E3%80%82">http://nodejs.cn/download/下载msi版本安装。</a></p>
<p>安装之后，命令行窗口，表示安装成功。</p>
<p><img src="http://iubest.gitee.io/pic/640-1601000484396.webp" alt="img"></p>
<p>给项目打包依赖</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 1、安装淘宝镜像依赖</span><br><span class="line">npm install -g cnpm --registry&#x3D;https:&#x2F;&#x2F;registry.npm.taobao.org</span><br><span class="line"># 2、安装项目依赖</span><br><span class="line">cnpm install</span><br><span class="line"># 启动服务</span><br><span class="line">npm run dev</span><br></pre></td></tr></table></figure>

<p>启动成功后会自动打开链接：<a target="_blank" rel="noopener" href="http://localhost:9527/">http://localhost:9527/</a></p>
<h3 id="后端"><a href="#后端" class="headerlink" title="后端"></a>后端</h3><p>首先clone项目下来（v2.5版本）：<a target="_blank" rel="noopener" href="https://gitee.com/geek_qi/cloud-platform/tree/v2.5/">https://gitee.com/geek_qi/cloud-platform/tree/v2.5/</a></p>
<p>导入到idea中，然后导入数据库sql：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601000484471.webp" alt="img"></p>
<p>修改数据库的账号密码（直接ctrl+shirt+r，搜索datasource:，可以很方便修改）：</p>
<p><img src="http://iubest.gitee.io/pic/640-1601000484443.webp" alt="img"></p>
<p>接下来启动redis，然后安装顺序启动我们的服务</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 启动顺序</span><br><span class="line">CenterBootstrap -&gt; AuthBootstrap -&gt; AdminBootstrap -&gt; GatewayServerBootstrap</span><br></pre></td></tr></table></figure>

<p>其他监控或服务可以先不启动。</p>
<h2 id="服务说明"><a href="#服务说明" class="headerlink" title="服务说明"></a>服务说明</h2><h3 id="ace-auth-server"><a href="#ace-auth-server" class="headerlink" title="ace-auth-server"></a>ace-auth-server</h3><p>关键类：</p>
<ul>
<li><p>AuthController</p>
</li>
<li><ul>
<li>/jwt开头的控制器，登录、刷新、校验jwt</li>
<li>网关不拦截这个链接的请求</li>
</ul>
</li>
<li><p>ClientController</p>
</li>
<li><ul>
<li>对外暴露的接口，可通过客户端的id和密钥获取到对应的授权相关资源</li>
</ul>
</li>
<li><p>ServiceController</p>
</li>
<li><ul>
<li>后台管理系统服务管理模块接口</li>
</ul>
</li>
<li><p>ClientTokenInterceptor</p>
</li>
<li><ul>
<li>拦截feign接口发起的请求，并自动添加请求头token</li>
</ul>
</li>
<li><p>ServiceAuthRestInterceptor</p>
</li>
<li><ul>
<li>拦截的url为/service/**（WebConfiguration）</li>
<li>服务之间的调用鉴权</li>
<li>一般feign、或者restTemplate方式调用</li>
</ul>
</li>
<li><p>UserAuthRestInterceptor</p>
</li>
<li><ul>
<li>拦截的url为/service/**（WebConfiguration）</li>
<li>获取用户的token并解析，为会话上下文添加用户信息（ThreadLocal）</li>
</ul>
</li>
<li><p>OkHttpTokenInterceptor</p>
</li>
<li><ul>
<li>拦截所有的feign请求，OkHttp重写请求</li>
<li>OkHttp3一个强有力的机制，能够监控，重写以及重试（请求的）调用</li>
</ul>
</li>
</ul>
<h2 id="模块分析"><a href="#模块分析" class="headerlink" title="模块分析"></a>模块分析</h2><p><img src="http://iubest.gitee.io/pic/640-1601000484398.webp" alt="img"></p>
<h3 id="用户授权"><a href="#用户授权" class="headerlink" title="用户授权"></a>用户授权</h3><p>首先我们来弄清楚一下登录的流程，登录中和之后发生了什么事，那么我们打开前段登录页面，按下F12，然后点击登录，可以查看到以下几个动作：</p>
<ul>
<li>点击登录，提交表单：<a target="_blank" rel="noopener" href="http://localhost:9527/api/auth/jwt/token">http://localhost:9527/api/auth/jwt/token</a></li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#返回值</span><br><span class="line">&#123;&quot;status&quot;:200,&quot;data&quot;:&quot;eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInVzZXJJZCI6IjEiLCJuYW1lIjoiTXIuQUciLCJleHAiOjE1NjE0NDEyNTZ9.tXNw8nhAFmI4QIQDpKy4DzWtJSTpfwD4685JqbA2pGScdyfXt_5DDs_r1gVZA4CwQC4oZxBsmLKZGclTLGc4HKeXlP2PiVoHZfSWymFRLNfvFqOzKUETJ6WpyDqK55yjf1wddTBD3VzSFvY49uunvozEcb2oFjOs3M_I2sgxAAU&quot;,&quot;rel&quot;:false&#125;</span><br></pre></td></tr></table></figure>

<p>可以看到登录成功之后返回的是一个jwt的token值，应该就是标识用户身份用的token。</p>
<ul>
<li>ajax发起请求获取用户信息以及菜单列表等：<a target="_blank" rel="noopener" href="http://localhost:9527/api/admin/user/front/info?token=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInVzZXJJZCI6IjEiLCJuYW1lIjoiTXIuQUciLCJleHAiOjE1NjE0NDEyNTZ9.tXNw8nhAFmI4QIQDpKy4DzWtJSTpfwD4685JqbA2pGScdyfXt_5DDs_r1gVZA4CwQC4oZxBsmLKZGclTLGc4HKeXlP2PiVoHZfSWymFRLNfvFqOzKUETJ6WpyDqK55yjf1wddTBD3VzSFvY49uunvozEcb2oFjOs3M_I2sgxAAU">http://localhost:9527/api/admin/user/front/info?token=eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInVzZXJJZCI6IjEiLCJuYW1lIjoiTXIuQUciLCJleHAiOjE1NjE0NDEyNTZ9.tXNw8nhAFmI4QIQDpKy4DzWtJSTpfwD4685JqbA2pGScdyfXt_5DDs_r1gVZA4CwQC4oZxBsmLKZGclTLGc4HKeXlP2PiVoHZfSWymFRLNfvFqOzKUETJ6WpyDqK55yjf1wddTBD3VzSFvY49uunvozEcb2oFjOs3M_I2sgxAAU</a></li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># 返回值</span><br><span class="line">&#123;&quot;id&quot;:&quot;1&quot;,&quot;username&quot;:&quot;admin&quot;,&quot;name&quot;:&quot;Mr.AG&quot;,&quot;description&quot;:&quot;&quot;,&quot;menus&quot;:[&#123;&quot;code&quot;:&quot;userManager&quot;,&quot;type&quot;:&quot;menu&quot;,&quot;uri&quot;:&quot;&#x2F;admin&#x2F;user&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;name&quot;:&quot;访问&quot;,&quot;menu&quot;:&quot;用户管理&quot;&#125;,&#123;&quot;code&quot;:&quot;baseManager&quot;,&quot;type&quot;:&quot;menu&quot;,&quot;uri&quot;:&quot;&#x2F;admin&quot;,&quot;method&quot;:&quot;GET&quot;,&quot;name&quot;:&quot;访问&quot;,&quot;menu&quot;:&quot;基础配置管理&quot;&#125;,</span><br><span class="line">.....(此次删除了部分),&#123;&quot;code&quot;:&quot;serviceManager:btn_clientManager&quot;,&quot;type&quot;:&quot;button&quot;,&quot;uri&quot;:&quot;&#x2F;auth&#x2F;service&#x2F;&#123;*&#125;&#x2F;client&quot;,&quot;method&quot;:&quot;POST&quot;,&quot;name&quot;:&quot;服务授权&quot;,&quot;menu&quot;:&quot;服务管理&quot;&#125;]&#125;</span><br></pre></td></tr></table></figure>

<p>同时注意请求头的信息：带有刚才登录后的jwt token，名称叫Authorization，以后的所有请求都会带上这个Authorization用于标识用户身份。 <img src="http://iubest.gitee.io/pic/640-1601000484473.webp" alt="img"></p>
<p>接下来我们来走下这个过程：</p>
<ul>
<li><p>客户端点击按钮发起登录请求<a target="_blank" rel="noopener" href="http://localhost:9527/api/auth/jwt/token">http://localhost:9527/api/auth/jwt/token</a></p>
</li>
<li><ul>
<li>获取请求uri，method</li>
<li>判断是否是不拦截地址</li>
<li>刚好我们发现不拦截地址中有这个配置：gate: ignore: startWith: /auth/jwt</li>
<li>所以网关过滤器直接将请求代理到ace-auth服务</li>
</ul>
</li>
<li><ul>
<li>到达网关gate首先经过我们的过滤器AccessGatewayFilter</li>
</ul>
</li>
<li><p>网关ace-gateway-v2的代理以及过滤配置</p>
</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"> # 网关代理规则</span><br><span class="line"> spring:</span><br><span class="line">   cloud:</span><br><span class="line">      gateway:</span><br><span class="line">        locator:</span><br><span class="line">          enabled: true</span><br><span class="line">        routes:</span><br><span class="line">        # &#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</span><br><span class="line">        - id: ace-auth</span><br><span class="line">          uri: lb:&#x2F;&#x2F;ace-auth</span><br><span class="line">          order: 8000</span><br><span class="line">          predicates:</span><br><span class="line">          - Path&#x3D;&#x2F;api&#x2F;auth&#x2F;**</span><br><span class="line">          filters:</span><br><span class="line">          - StripPrefix&#x3D;2</span><br><span class="line">        - id: ace-admin</span><br><span class="line">          uri: lb:&#x2F;&#x2F;ace-admin</span><br><span class="line">          order: 8001</span><br><span class="line">          predicates:</span><br><span class="line">          - Path&#x3D;&#x2F;api&#x2F;admin&#x2F;**</span><br><span class="line">          filters:</span><br><span class="line">          - StripPrefix&#x3D;2</span><br><span class="line">gate:</span><br><span class="line">  ignore:</span><br><span class="line">    startWith: &#x2F;auth&#x2F;jwt</span><br></pre></td></tr></table></figure>

<p>那么我们进入ace-auth中找到对应controller 可以找到这里：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">@RestController</span><br><span class="line">@RequestMapping(&quot;jwt&quot;)</span><br><span class="line">@Slf4j</span><br><span class="line">public class AuthController &#123;    @RequestMapping(value &#x3D; &quot;token&quot;, method &#x3D; RequestMethod.POST)</span><br><span class="line">    public ObjectRestResponse&lt;String&gt; createAuthenticationToken(</span><br><span class="line">            @RequestBody JwtAuthenticationRequest authenticationRequest) throws Exception &#123;</span><br><span class="line">        log.info(authenticationRequest.getUsername()+&quot; require logging...&quot;);</span><br><span class="line">        final String token &#x3D; authService.login(authenticationRequest);</span><br><span class="line">        return new ObjectRestResponse&lt;&gt;().data(token);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个方法里面只有一个方法就是authService.login，点这个方法进去发现里面又有个方法是<strong>userService</strong>.validate，这是一个feign接口</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">@FeignClient(value &#x3D; &quot;ace-admin&quot;,configuration &#x3D; FeignConfiguration.class)</span><br><span class="line">public interface IUserService &#123;  @RequestMapping(value &#x3D; &quot;&#x2F;api&#x2F;user&#x2F;validate&quot;, method &#x3D; RequestMethod.POST)</span><br><span class="line">  public UserInfo validate(@RequestBody JwtAuthenticationRequest authenticationRequest);</span><br></pre></td></tr></table></figure>

<p>}可以看到这是远程调用，那么接下来，我们看看远程调用的过程，这设计到服务间的相互鉴权</p>
<h3 id="服务间鉴权"><a href="#服务间鉴权" class="headerlink" title="服务间鉴权"></a>服务间鉴权</h3><p>上面的<strong>userService</strong>.validate，类上有这样的注解</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">@FeignClient(value &#x3D; &quot;ace-admin&quot;,configuration &#x3D; FeignConfiguration.class)</span><br><span class="line">表示声明式调用ace-admin服务，接下来我们解析一下这个过程发生了什么事情，我们可以看到有个configuration &#x3D; FeignConfiguration.class，我们打开FeignConfiguration</span><br><span class="line">@Configuration</span><br><span class="line">public class FeignConfiguration &#123;</span><br><span class="line">    @Bean</span><br><span class="line">    ClientTokenInterceptor getClientTokenInterceptor()&#123;</span><br><span class="line">        return new ClientTokenInterceptor();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>再打开ClientTokenInterceptor ：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">public class ClientTokenInterceptor implements RequestInterceptor &#123;</span><br><span class="line">    private Logger logger &#x3D; LoggerFactory.getLogger(ClientTokenInterceptor.class);</span><br><span class="line">    @Autowired</span><br><span class="line">    private ClientConfiguration clientConfiguration;</span><br><span class="line">    @Autowired</span><br><span class="line">    private AuthClientService authClientService;    @Override</span><br><span class="line">    public void apply(RequestTemplate requestTemplate) &#123;</span><br><span class="line">        logger.info(&quot;----&gt; 为feign调用添加token头&quot;);</span><br><span class="line">        try &#123;</span><br><span class="line">            requestTemplate.header(clientConfiguration.getClientTokenHeader(), authClientService.apply(clientConfiguration.getClientId(), clientConfiguration.getClientSecret()));</span><br><span class="line">        &#125; catch (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">从上面的配置我们知道，RequestInterceptor 是feign接口下的包装拦截器，从代码里面看到，其实意思就是在feign发起远程调用时候往请求里添加请求头信息（clientToken），所以发起的请求头中就有了当前服务的身份token，接受端的服务器就能根据token辨别来源服务器的身份。</span><br></pre></td></tr></table></figure>

<p>接下来我们看看接收端怎么辨别的。ServiceAuthRestInterceptor</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">public class ServiceAuthRestInterceptor extends HandlerInterceptorAdapter &#123;</span><br><span class="line">    private Logger logger &#x3D; LoggerFactory.getLogger(ServiceAuthRestInterceptor.class);    @Override</span><br><span class="line">    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception &#123;        logger.info(&quot;------&gt;判断服务A是否有权限访问当前服务B ~&quot;);</span><br><span class="line">        HandlerMethod handlerMethod &#x3D; (HandlerMethod) handler;</span><br><span class="line">        &#x2F;&#x2F; 配置该注解，说明不进行服务拦截</span><br><span class="line">        IgnoreClientToken annotation &#x3D; handlerMethod.getBeanType().getAnnotation(IgnoreClientToken.class);</span><br><span class="line">        if (annotation &#x3D;&#x3D; null) &#123;</span><br><span class="line">            annotation &#x3D; handlerMethod.getMethodAnnotation(IgnoreClientToken.class);</span><br><span class="line">        &#125;</span><br><span class="line">        if(annotation!&#x3D;null) &#123;</span><br><span class="line">            return super.preHandle(request, response, handler);</span><br><span class="line">        &#125;        String token &#x3D; request.getHeader(serviceAuthConfig.getTokenHeader());</span><br><span class="line">        IJWTInfo infoFromToken &#x3D; serviceAuthUtil.getInfoFromToken(token);</span><br><span class="line">        String uniqueName &#x3D; infoFromToken.getUniqueName();</span><br><span class="line">        for(String client:serviceAuthUtil.getAllowedClient())&#123; &#x2F;&#x2F;ace-auth、ace-gate</span><br><span class="line">            if(client.equals(uniqueName))&#123;</span><br><span class="line">                return super.preHandle(request, response, handler);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        throw new ClientForbiddenException(&quot;Client is Forbidden!&quot;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从代码看到首先看下有没IgnoreClientToken的注解，有的话就跳过。没有的话继续获取调用端服务器的token，然后再去获取当前服务器允许访问的lient（<strong>serviceAuthUtil</strong>.getAllowedClient()），然后匹配调用端的名称是否在允许的客户端内，如果允许就继续，不允许就forbidden。所以挺清晰的。那这套客户端和允许的访问的客户端这套关系是哪里维护的呢，其实有两张表</p>
<p><img src="http://iubest.gitee.io/pic/640-1601000484453.webp" alt="img"></p>
<ul>
<li>auth_client ：客户端的id和名称等</li>
<li>auth_client_service：服务端允许调用的客户端关联表</li>
</ul>
<p>从这张表中，我们就可以得出哪些允许被访问，哪些不能被访问了</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>第一件事</p>
<ul>
<li>新建所有需要用到的模块，基本搭建好框架</li>
<li>做好增删改查的封装、接口规范</li>
<li>全局异常捕捉封装</li>
<li>单元测试</li>
<li>通用工具类</li>
</ul>
<p>第二件事</p>
<ul>
<li>决定服务的授权模式（jwt、oauth2等）</li>
</ul>
<p>第三件事</p>
<ul>
<li>服务内部鉴权</li>
<li>权限控制</li>
</ul>
<p>视频讲解：<a target="_blank" rel="noopener" href="https://www.bilibili.com/video/bv1SD4y1o7cN">https://www.bilibili.com/video/bv1SD4y1o7cN</a></p>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，著作权归作者所有。转载请注明出处！
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://example.com/2020/11/11/develop/%E5%9F%BA%E4%BA%8ESpring%20Cloud%E5%BE%AE%E6%9C%8D%E5%8A%A1%E5%8C%96%E5%BC%80%E5%8F%91%E5%B9%B3%E5%8F%B0Cloud-Platform%E5%AE%8C%E6%95%B4%E8%A7%A3%E6%9E%90/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/develop/" rel="tag">develop</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2020/11/11/develop/%E5%9C%A8Python%E4%B8%AD%E4%BD%BF%E7%94%A8httpx%E6%A8%A1%E5%9D%97/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            在Python中使用httpx模块.md
          
        </div>
      </a>
    
    
      <a href="/2020/11/11/develop/%E5%BC%80%E6%BA%90%E6%8E%A8%E8%8D%90%20CoDo%E5%BC%80%E6%BA%90%E4%B8%80%E7%AB%99%E5%BC%8FDevOps%E5%B9%B3%E5%8F%B0/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">开源推荐 CoDo开源一站式DevOps平台.md</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "",
    app_key: "",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "给我的文章加点评论吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2020
        <i class="ri-heart-fill heart_icon"></i> TzWind
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="Hexo"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" target="_blank" rel="noopener" href="http://www.baidu.com">百度</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2019/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>